#include "CubeApp.h"

#define GLM_FORCE_RADIANS
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#include <glm/glm.hpp>
#include <glm/gtc/constants.hpp>

#include <stdexcept>
#include <array>
#include <cassert>
#include <chrono>

#include "LvCamera.h"
#include "SimpleRenderSystem.h"
#include "KeyboardMovementController.h"
#include "LvBuffer.hpp"
#include "LvFrameInfo.h"

const float PI_2 = glm::two_pi<float>();
const float PI = glm::pi<float>();

struct GlobalUbo
{
	glm::mat4 projectionView{ 1.0f };
	glm::vec3 lightDirecton{ glm::normalize(glm::vec3{1.0f, -3.0f, -1.0f}) };
};

CubeApp::CubeApp()
{
    globalPool = LvDescriptorPool::Builder(lvDevice)
                     .setMaxSets(LvSwapChain::MAX_FRAMES_IN_FLIGHT)
                     .addPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, LvSwapChain::MAX_FRAMES_IN_FLIGHT)
                     .build();
	loadGameObjects();

}

CubeApp::~CubeApp()
{
}

void CubeApp::run()
{
	std::vector<std::unique_ptr<LvBuffer>> uboBuffers(LvSwapChain::MAX_FRAMES_IN_FLIGHT);
	for (int i = 0; i < uboBuffers.size(); i++)
	{
		uboBuffers[i] = std::make_unique<LvBuffer>(
			lvDevice, sizeof(GlobalUbo), 1,
			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT,
			lvDevice.properties.limits.minUniformBufferOffsetAlignment );
		uboBuffers[i]->map();
	}

    auto globalSetLayout = LvDescriptorSetLayout::Builder(lvDevice)
                               .addBinding(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_ALL_GRAPHICS)
                               .build();

    std::vector<VkDescriptorSet> globalDescriptorSets(LvSwapChain::MAX_FRAMES_IN_FLIGHT);
    for (int i = 0; i < globalDescriptorSets.size(); i++)
    {
        auto bufferInfo = uboBuffers[i]->descriptorInfo();
        LvDescriptorWriter(*globalSetLayout, *globalPool).writeBuffer(0, &bufferInfo).build(globalDescriptorSets[i]);
    }

	SimpleRenderSystem simpleRenderSystem{ lvDevice, lvRenderer.getSwapChainRendPass(), globalSetLayout->getDescriptorSetLayout() };
	LvCamera camera;
	auto viewerObject = LvGameObject::createGameObject();
	KeyboardMovementController cameraController;

	auto currentTime = std::chrono::high_resolution_clock::now();
	GlobalUbo ubo{};
	while (!lvWindow.shouldClose())
	{
		glfwPollEvents();

		auto newTime = std::chrono::high_resolution_clock::now();
		float frameTime = std::chrono::duration<float, std::chrono::seconds::period>(newTime - currentTime).count();
		currentTime = newTime;

		cameraController.moveInPlaneZX(lvWindow.getGLFWwindow(), frameTime, viewerObject);
		camera.setViewYXZ(viewerObject.transform.translation, viewerObject.transform.rotation);

		float aspect = lvRenderer.getAspectRatio();
		//camera.setOrthographicProjection(-aspect, aspect, -1, 1, -10, 10);
		camera.setPerspectiveProjection(glm::radians(60.0f), aspect, 0.1f, 100.0f);

		if (auto commandBuffer = lvRenderer.beginFrame())
		{
			int frameIndex = lvRenderer.getFrameIndex();
			FrameInfo frameInfo{frameIndex, frameTime, commandBuffer, camera, globalDescriptorSets[frameIndex]};

			ubo.projectionView = camera.getProjection() * camera.getView();
			uboBuffers[frameIndex]->writeToBuffer(&ubo);
			uboBuffers[frameIndex]->flush();

			lvRenderer.beginSwapChainRenderPass(commandBuffer);

			simpleRenderSystem.renderGameObjects(frameInfo, gameObjects);

			lvRenderer.endSwapChainRenderPass(commandBuffer);
			lvRenderer.endFrame();
		}
	}
	vkDeviceWaitIdle(lvDevice.device());
}


void CubeApp::loadGameObjects()
{
	std::shared_ptr<LvModel> lvModel = LvModel::createModelFromFile(lvDevice, "../models/smooth_vase.obj");

	auto smoothVase = LvGameObject::createGameObject();
	smoothVase.model = lvModel;
	smoothVase.transform.translation = { -0.5f, 0.5f, 2.5f };
	smoothVase.transform.scale = { 2.5f, 2.5f, 2.5f };

	gameObjects.push_back(std::move(smoothVase));

	lvModel = LvModel::createModelFromFile(lvDevice, "../models/flat_vase.obj");

	auto flatVase = LvGameObject::createGameObject();
	flatVase.model = lvModel;
	flatVase.transform.translation = { 0.5f, 0.5f, 2.5f };
	flatVase.transform.scale = { 3.5f, 1.5f, 3.5f };

	gameObjects.push_back(std::move(flatVase));
}





